'use strict';

const fs = require('fs')
const path = require('path');
const userHome = require('os').homedir()
const inquirer = require('inquirer')
const glob = require('glob')
const ejs = require('ejs')
const fse = require('fs-extra')
const semver = require('semver')
const Command = require('@fobiz-cli/command')
const Package = require('@fobiz-cli/package')
const log = require('@fobiz-cli/log')
const {spinnerStart, sleep, execAsync} = require('@fobiz-cli/utils')
const getTemplate = require('./getProjectTemplate')

const TYPE_PROJECT = 'project'
const TYPE_COMPONENT = 'component'

const TEMPLATE_TYPE_NORMAL = 'normal'
const TEMPLATE_TYPE_CUSTOM = 'custom'

const WHITE_LIST_COMMANDS = ['npm', 'cnpm']

class InitCommand extends Command {
    init() {
        this.projectName = this._argv[0] || ''
        this.force = !!this.opts.force
        log.verbose('project name', this.projectName)
        log.verbose('force create', this.force)
    }

    async exec() {
        this.projectInfo = await this.prepare()
        if (!this.projectInfo) return;
        this.downloadTmpInfo = this.templateList.find(it => {
            return it.npmName === this.projectInfo.templateName
        })
        await this.downloadTemplate(this.downloadTmpInfo)
        await this.installTemplate()
    }

    async downloadTemplate(templateInfo) {
        const targetPath = path.resolve(userHome, process.env.CLI_HOME_PATH, 'template')
        const storeDir = path.resolve(userHome, process.env.CLI_HOME_PATH, 'template', 'node_modules')
        const {npmName, version} = templateInfo
        this.templateNpm = new Package({
            targetPath, storeDir, packageName: npmName, packageVersion: version
        })
        if (!await this.templateNpm.exists()) {
            await this.templateNpm.install()
        } else {
            await this.templateNpm.update()
        }
    }

    async installTemplate() {
        log.verbose('template package', this.templateNpm)
        if (!this.downloadTmpInfo.type) {
            this.downloadTmpInfo.type = TEMPLATE_TYPE_NORMAL
        }
        if (this.downloadTmpInfo.type === TEMPLATE_TYPE_NORMAL) {
            await this.installNormalTemplate()
        } else if (this.downloadTmpInfo.type === TEMPLATE_TYPE_CUSTOM) {
            await this.installCustomTemplate()
        } else {
            throw new Error('无法识别模板类型')
        }
    }

    ejsRender(options) {
        const cwd = path.resolve(process.cwd(), this.projectName)
        return new Promise((resolve, reject) => {
            glob('**', {
                cwd,
                ignore: options.ignore || '',
                nodir: true
            }, (err, files) => {
                if (err) {
                    reject(err)
                }
                Promise.all(files.map(file => {
                    const filePath = path.join(cwd, file)
                    return new Promise((resolve1, reject1) => {
                        ejs.renderFile(filePath, this.projectInfo, {}, (err, str) => {
                            if (err) {
                                reject1(err)
                            } else {
                                fse.writeFileSync(filePath, str)
                                resolve1(str)
                            }
                        })
                    })
                })).then(resolve).catch(reject)
            })
        })
    }

    async installNormalTemplate() {
        log.verbose('template info', this.downloadTmpInfo)
        // 拷贝模板到当前目录
        let spinner = spinnerStart({
            text: '正在下载模板...'
        })
        await sleep()
        try {
            const templatePath = path.resolve(this.templateNpm.pkgCachePath, 'template')
            const targetPath = path.resolve(process.cwd(), this.projectName)
            fse.ensureDirSync(templatePath)
            fse.ensureDirSync(targetPath)
            fse.copySync(templatePath, targetPath)
        } catch (e) {
            spinner.fail('模板安装失败')
            throw e
        } finally {
            spinner.succeed('模板安装成功')
        }
        await this.ejsRender({ignore: ['node_modules/**', ...(this.downloadTmpInfo.ignore || [])]})
        const {installCommand, startCommand} = this.downloadTmpInfo
        // 安装依赖
        if (installCommand) {
            await this.execCommand(installCommand, '依赖安装过程中失败！')
        }
        // 启动命令
        if (startCommand) {
            await this.execCommand(startCommand, '启动过程中失败！')
        }

    }

    checkCmd(cmd) {
        if (WHITE_LIST_COMMANDS.includes(cmd)) {
            return cmd
        }
        return null
    }

    async execCommand(command, errorMsg) {
        const cmdArray = command.split(' ')
        const cmd = this.checkCmd(cmdArray[0])
        if (!cmd) {
            throw new Error(`命令不存在，命令：${cmd}`)
        }
        const args = cmdArray.slice(1)
        const execRet = await execAsync(cmd, args, {
            cwd: path.resolve(process.cwd(), this.projectName),
            stdio: 'inherit'
        })
        if (execRet !== 0) {
            throw new Error(errorMsg)
        }
    }

    async installCustomTemplate() {
        console.log('安装自定义模板');
    }

    async prepare() {
        // 判断模板是否存在
        this.templateList = await getTemplate()
        if (!this.templateList || !this.templateList.length) {
            throw new Error('模板列表不存在')
        }
        const isDirValid = await this.prepareDir()
        if (isDirValid) {
            return await this.getProjectInfo()
        }
    }

    async prepareDir() {
        const localPath = path.resolve(process.cwd(), this.projectName);
        if (!this.isCwdEmpty(localPath)) {
            let ifContinue = false
            if (!this.force) {
                ifContinue = (await inquirer.prompt({
                    type: 'confirm',
                    message: '当前文件夹不为空，是否继续创建项目？',
                    name: 'ifContinue',
                    default: false
                })).ifContinue
            }
            if (ifContinue || this.force) {
                // 二次确认
                const {confirmDelete} = await inquirer.prompt({
                    type: 'confirm',
                    name: 'confirmDelete',
                    default: false,
                    message: '是否清空当前文件夹下的所有文件'
                })
                if (confirmDelete) {
                    let spinner = spinnerStart('正在清空目录...')
                    fse.emptyDirSync(localPath)
                    spinner.succeed('目录已清空')
                }
                return true
            }
        } else {
            return true
        }
    }

    async getProjectInfo() {
        const {type} = await inquirer.prompt({
            type: 'list',
            name: 'type',
            choices: [{
                name: '项目',
                value: TYPE_PROJECT
            }, {
                name: '组件',
                value: TYPE_COMPONENT
            }],
            message: '请选择初始化类型：',
            default: TYPE_PROJECT
        })

        const isValidProjectName = name => {
            return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(name)
        }
        let projectInfo = {}
        this.selectTemplateList = this.templateList.filter(template => template.tag.includes(type))
        const title = type === TYPE_PROJECT ? '项目' : '组件'
        const projectInfoPrompt = [{
            type: 'input',
            name: 'name',
            message: `请输入${title}名称：`,
            default: '',
            validate: function(v) {
                const done = this.async();

                // Do async stuff
                setTimeout(function() {
                    /**
                     * 1. 首字母必须为英文字符
                     * 2. 尾字符必须为英文或数字，不能为字符
                     * 3. 字符仅允许“-_”
                     */
                    if (!isValidProjectName(v)) {
                        // Pass the return value in the done callback
                        done(`请输入合法的${title}名称`);
                        return;
                    }
                    // Pass the return value in the done callback
                    done(null, true);
                }, 0);
            },
            filter: function(v) {
                return v
            }
        }, {
            type: 'input',
            name: 'version',
            message: '请输入版本号：',
            default: '1.0.0',
            validate: function(v) {
                const done = this.async();

                setTimeout(function() {
                    if (!semver.valid(v)) {
                        done('请输入合法的版本号');
                        return;
                    }
                    done(null, true);
                }, 0);
            },
            filter: function(v) {
                if (semver.valid(v)) {
                    return semver.valid(v)
                }
            }
        }, {
            type: 'list',
            name: 'templateName',
            choices: this.selectTemplateList.map(item => {
                return {
                    name: item.name,
                    value: item.npmName
                }
            }),
            message: `请选择${title}模板：`,
            default: this.selectTemplateList[0].npmName
        }]
        if (isValidProjectName(this.projectName)) {
            projectInfoPrompt.shift()
            projectInfo.name = this.projectName
        }
        if (type === TYPE_PROJECT) {
            projectInfo = {...(await inquirer.prompt(projectInfoPrompt)), ...projectInfo}
        } else if (type === TYPE_COMPONENT) {
            const descriptionPrompt = {
                type: 'input',
                name: 'description',
                message: '请输入组件描述信息：',
                default: '',
                validate: function (v) {
                    const done = this.async();
                    setTimeout(function() {
                        if (!v) {
                            // Pass the return value in the done callback
                            done(`请输入组件描述信息`);
                            return;
                        }
                        // Pass the return value in the done callback
                        done(null, true);
                    }, 0);
                }
            }
            projectInfoPrompt.push(descriptionPrompt)
            projectInfo = await inquirer.prompt(projectInfoPrompt)
        }
        if (projectInfo.name) {
            projectInfo.className = require('kebab-case')(projectInfo.name).replace(/^-/, '')
        }
        log.verbose('project info', projectInfo)
        return {type, ...projectInfo}
    }

    isCwdEmpty(localPath) {
        fse.ensureDirSync(localPath)
        const dirFiles = fs.readdirSync(localPath)
        return !dirFiles.filter(filename => {
            return !filename.startsWith('.') && 'node_modules' !== filename
        }).length
    }
}

function init(argv) {
    return new InitCommand(argv)
}

module.exports = init;
